/*	--- 单例 --- 

# 保证【一个类】仅有【一个实例】，并提供一个【访问它的全局访问点】。

------------------------------------------------------


【单例模式】（Singleton）的目的是为了保证在【一个进程】中，【某个类】有且仅有【一个实例】。
(也就是说, 某个class只实现了唯一一个Instance)


因为【这个类】只有【一个实例】，因此，自然【不能】让【调用方】使用【new Xyz()】来【创建实例】了。

所以，【单例】的【构造方法】必须是【private】，这样就【防止了调用方自己创建实例】，但是在【类的内部】，是可以用【一个静态字段】来引用【唯一创建的实例】的： */
public class Singleton {
	// 静态Field引用唯一的Instance
	private static final Singleton INSTANCE = new Singleton();

	// private 构造方法 保证了外部无法new
	private Singleton() {
		// ...
	}
}


/*
那么问题来了，【外部调用方】如何获得【这个唯一实例】？

答案是提供【一个静态方法】，直接返回【实例】： */
public class Singleton {
	// static Field 引用 唯一Instance
	private static final Singleton INSTANCE = new Singleton();

	// static Method 返回 Instance
	public static Singleton getInstance() {
		return INSTANCE;
	}

	// private 构造方法 保证外部无法new
	private Singleton() {
		// 构造...
	}
}


/*
或者直接把【static变量】暴露给外部： */
public class Singleton {
	// static Method 引用 唯一Instance
	public static final Singleton INSTANCE = new Singleton();
		// public

	// private构造方法 保证外部无法实例化
	private Singleton() {
		// structure...
	}
}


/*
所以，【单例模式】的【实现方式】很简单：

	1. 只有【private构造方法】，确保外部无法实例化；
	2. 通过【private static Field变量】持有【唯一实例】，保证【全局唯一性】；
	3. 通过【public static Method方法】返回【此唯一实例】，使【外部调用方】能获取到【实例】。


【Java标准库】有【一些类】就是【单例】，例如【Runtime】这个类： */
Runtime runtime = Runtime.getRuntime();


/*
有些童鞋可能听说过【延迟加载】，即在【调用方】第一次调用【getInstance()】时才【初始化全局唯一实例】，类似这样： */
public class Singleton {  // 多线程中, 错误示范
	private static Singleton INSTANCE = null;

	private Singleton() {
		// structure...
	}

	public static Singleton getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new Singleton();
		}

		return INSTANCE;
	}
	
}


/*
遗憾的是，【延迟加载】这种【写法】在【多线程】中是【错误】的，在【竞争条件下】会【创建】出【多个实例】。

必须对【整个方法】进行【加锁】： */
public class Singleton {
	private static Singleton INSTANCE = null;

	private Singleton() {
		// structure...
	}

	public synchronized static Singleton getInstance() {
	// synchronized
		if (INSTANCE == null) {
			INSTANCE = new Singleton();
		}

		return INSTANCE;
	}

}


/*
但【加锁】会严重影响【并发性能】。

还有些童鞋听说过【双重检查】，类似这样： */
public class Singleton {
	private static final Singleton INSTANCE = null;

	private Singleton() {
		// structure
	}

	public static Singleton getInstance() {
		if (INSTANCE == null) {
			synchronized (Singleton.class) { // 双重检查
				if (INSTANCE == null) { 
					INSTANCE = new Singleton();
				}
			}
		}

		return INSTANCE;
	}
}


/*
然而，由于【Java的内存模型】，【双重检查】在这里【不成立】。

要真正实现【延迟加载】，只能通过【Java的ClassLoader机制】完成。

如果【没有特殊的需求】，使用【Singleton模式】的时候，最好不要【延迟加载】，这样会使代码更简单。

另一种【实现Singleton的方式】是利用【Java的enum】，因为【Java】保证【enum枚举类】的【每个枚举 都是 单例】，

所以我们只需要编写一个【只有一个枚举的类】即可： */
public enum World {
	// 唯一枚举
	INSTANCE;

	private String name = "world";

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}

}


/*
【枚举类】也完全可以像【其他类】那样定义【自己的字段、方法】，这样上面这个【World类】在【调用方】看来就可以这么用： */
String name = World.INSTANCE.getName();


/*
使用【枚举】实现【Singleton】还避免了【第一种方式】实现Singleton的【一个潜在问题】：  即【序列化】和【反序列化】会绕过【普通类的private构造方法】从而【创建】出【多个实例】，而【枚举类】就没有这个问题。


Q: 那我们什么时候应该用【Singleton】呢？
A: 实际上，【很多程序】，尤其是【Web程序】，【大部分の服务class】都应该【被视作 Singleton】，如果全部按【Singleton的写法】写，会非常麻烦，

所以，通常是通过【约定】让【框架】（例如Spring）来【实例化】这些【类】，保证【只有一个实例】，【调用方】自觉通过【框架】获取【实例】而不是【new操作符】： */

@Component // 表示这是一个【单例组件】
public class MyService {
	// ...
}


/*
因此，除非【确有必要】，否则【Singleton模式】一般以【“约定”】为主，【不要刻意】实现它。



=======================================================

#	练习

使用【2种Singleton模式】实现【单例】： */
public enum Singleton_1 {
	INSTANCE;

	private String name = "singleton";

	public void setName(String name) {
		this.name = name;
	}

	public void getName() {
		return this.name;
	}
}

@Component // 单例组件
public class MyService_Singleton {
	// ... 
}


public class Singleton {
	private static final Singleton INSTANCE = new Singleton();

	private Singleton() { // private structure
		// ...
	}

	public static Singleton getInstacne() {
		return INSTANCE;
	}
}

/*====================================================

#	----- 单例 の 小结 ----- 


1. 【Singleton模式】是为了保证一个程序的运行期间，【某个类】有且只有【一个全局唯一实例】；

2. 【Singleton模式】既可以【严格实现】，也可以以【约定的方式】把【普通类】视作【单例】。

3. 使用【Singleton模式】的时候，最好不要【延迟加载】，这样会使代码更简单, 且用了性价比较低。

*/














